/*
 *	Yalala
 *	
 *	Copyright © MediaMonks B.V.
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions are met:
 *	1. Redistributions of source code must retain the above copyright
 *	   notice, this list of conditions and the following disclaimer.
 *	2. Redistributions in binary form must reproduce the above copyright
 *	   notice, this list of conditions and the following disclaimer in the
 *	   documentation and/or other materials provided with the distribution.
 *	3. All advertising materials mentioning features or use of this software
 *	   must display the following acknowledgement:
 *	   This product includes software developed by MediaMonks B.V.
 *	4. Neither the name of MediaMonks B.V. nor the
 *	   names of its contributors may be used to endorse or promote products
 *	   derived from this software without specific prior written permission.
 *	
 *	THIS SOFTWARE IS PROVIDED BY MEDIAMONKS B.V. ''AS IS'' AND ANY
 *	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *	DISCLAIMED. IN NO EVENT SHALL MEDIAMONKS B.V. BE LIABLE FOR ANY
 *	DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *	
 */

package com.mediamonks.yalala.ui 
{
	import nl.acidcats.yalog.common.Levels;
	import nl.acidcats.yalog.common.MessageData;

	import temple.codecomponents.scroll.CodeScrollBar;
	import temple.common.enum.Orientation;
	import temple.ui.buttons.behaviors.ButtonBinder;
	import temple.ui.layout.liquid.LiquidContainer;
	import temple.ui.layout.liquid.LiquidSprite;

	import com.mediamonks.yalala.data.filters.CompositeFilter;
	import com.mediamonks.yalala.data.vo.SettingsData;
	import com.mediamonks.yalala.ui.table.Table;

	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	/**
	 * @author Thijs Broerse
	 */
	public class Viewer extends LiquidContainer
	{
		/**
		 * Max duration of a single thread. The SWF is running on 31 fps, so 1 frame takes 1000/31 miliseconds.
		 * Thread may only take .6 of this time
		 */
		private static const _MAX_THREAD_DURATION:int = int((1000 / 31) * 1);
		
		/**
		 * Length of the first 'test' tread to calculate the maximum number of calculation during one thread
		 */
		private static const _TEST_THREAD_LENGTH:int = 1000;
		
		private static const _NO_RESULT_MESSAGE:MessageData = new MessageData("no messages found", Levels.STATUS);

		private var _table:Table;
		private var _verticalScrollBar:CodeScrollBar;
		private var _horizontalScrollBar:CodeScrollBar;
		private var _background:LiquidSprite;

		private var _messages:Vector.<MessageData>;
		private var _splitDict:Dictionary;
		private var _filter:CompositeFilter;
		private var _settings:SettingsData;
		private var _filtered:Vector.<MessageData>;
		private var _updateNeeded:Boolean;
		private var _updateIndex:uint;

		public function Viewer(settings:SettingsData)
		{
			this._settings = settings;
			this._messages = new Vector.<MessageData>();
			this._filtered = new Vector.<MessageData>();
			this._filter = new CompositeFilter();
			this._splitDict = new Dictionary(true);
			
			this.createUI();
			
			this.addEventListener(Event.ENTER_FRAME, this.handleEnterFrame);
		}

		private function createUI():void 
		{
			// background
			this._background = new LiquidSprite();
			this._background.graphics.beginFill(0xFF0000, 0);
			this._background.graphics.drawRect(0, 0, 1, 1);
			this._background.graphics.endFill();
			this._background.left = this._background.right = this._background.top = this._background.bottom = 0;
			this.addChild(this._background);
			
			// table
			this._table = new Table();
			this._table.top = 0;
			this._table.left = 0;
			this._table.right = 14;
			this._table.bottom = 0;
			this.addChild(this._table);
			this._table.addEventListener(Event.RESIZE, this.handleTableResize);
			
			// vertical scrollbar
			this._verticalScrollBar = new CodeScrollBar();
			this._verticalScrollBar.top = 0;
			this._verticalScrollBar.bottom = 14;
			this._verticalScrollBar.right = 0;
			this.addChild(this._verticalScrollBar);
			this._verticalScrollBar.scrollPane = this._table;

			// vertical scrollbar
			this._horizontalScrollBar = new CodeScrollBar(Orientation.HORIZONTAL);
			this._horizontalScrollBar.left = 0;
			this._horizontalScrollBar.bottom = 0;
			this._horizontalScrollBar.right = 14;
			this.addChild(this._horizontalScrollBar);
			this._horizontalScrollBar.scrollPane = this._table;
			
			new ButtonBinder(this._background, this._table);
			
			//ScrollBar.scrollProxy = ScrollBehavior.scrollProxy = new TweenLitePropertyProxy(.1);
			
			this.addEventListener(MouseEvent.MOUSE_UP, this.handleMouseUp);
		}

		public function addMessage(messageData:MessageData):void 
		{
			this._messages.push(messageData);
			if (!this._updateNeeded)
			{
				this._updateNeeded = true;
				this.filterMessages();
			}
		}
		
		public function updateAllMessages():void 
		{
			this._updateNeeded = true;
			this._updateIndex = 0;
			this._filtered.length = 0;
			this._table.scrollV = 0;
		}
		
		private function handleEnterFrame(event:Event):void 
		{
			this.filterMessages();
		}

		private function filterMessages():void 
		{
			if (this._updateNeeded && this._updateIndex < this._messages.length)
			{
				var time:int = getTimer();
				
				var leni:int = Math.min(this._messages.length, this._updateIndex + _TEST_THREAD_LENGTH);
				
				while (this._updateIndex < leni)
				{
					if (this._filter.isValid(this._messages[this._updateIndex]))
					{
						this._filtered.push(this._messages[this._updateIndex]);
					}
					this._updateIndex++;
				}
				
				if (this._updateIndex < this._messages.length)
				{
					var duration:int = getTimer() - time;
					
					if (duration < _MAX_THREAD_DURATION)
					{
						// we have some time left, calculate how many messages we can handle
						leni = (_MAX_THREAD_DURATION - duration) / (duration / _TEST_THREAD_LENGTH);
						leni = Math.min(leni, this._messages.length);
						
						while (this._updateIndex < leni)
						{
							if (this._filter.isValid(this._messages[this._updateIndex]))
							{
								this._filtered.push(this._messages[this._updateIndex]);
							}
							this._updateIndex++;
						}
					}
				}
				
				var splitted:Vector.<MessageData> = new Vector.<MessageData>();
				leni = this._filtered.length;
				
				for (var i:int = 0; i < leni; i++)
				{
					splitted = splitted.concat(this.splitMessage(this._filtered[i]));
				}
				
				if (!splitted.length) splitted.push(_NO_RESULT_MESSAGE);
				
				this._table.data = splitted;
				
				if (this._updateIndex >= this._messages.length) this._updateNeeded = false;
				
				if (this._settings.autoScroll)
				{
					this._table.scrollVTo(this._table.maxScrollV);
				}
			}
		}
		
		public function clear():void 
		{
			this._messages.length = 0;
			this._table.clear();
			this.updateAllMessages();
		}

		public function clearConnection(id:Number):void
		{
			for (var i:int = this._messages.length - 1;i >= 0; --i)
			{
				if (this._messages[i].connectionId == id) this._messages.splice(i, 1);
			}
			this.updateAllMessages();
		}

		public function get filter():CompositeFilter
		{
			return this._filter;
		}
		
		/**
		 *	Split message into several messages if message contains newline
		 *	@param message: the message
		 *	@return an Array of objects of type MessageData
		 */
		private function splitMessage(message:MessageData):Vector.<MessageData> 
		{
			var splitted:Vector.<MessageData> = new Vector.<MessageData>();
			
			if (this._splitDict[message])
			{
				return this._splitDict[message];
			}
			else if (message.text.indexOf("\n") > -1) 
			{
				var stringList:Array = message.text.split("\n");
				var len:Number = stringList.length;
				for (var i:Number = 0;i < len;i++) 
				{
					// add new messages, set time only for first message, sender only for last message
					splitted.push(new MessageData(stringList[i], message.level, (i == 0) ? message.time : null, (i < len - 1) ? null : message.sender, (i < len - 1) ? 0 : message.senderId, (i < len - 1) ? null : message.stackTrace));
				}
			} 
			else 
			{
				splitted.push(message);
			}
			this._splitDict[message] = splitted;
			
			return splitted;
		}
		
		private function handleMouseUp(event:MouseEvent):void 
		{
			if (event.target is TextField)
			{
				var tf:TextField = TextField(event.target);
				var selection:String = tf.text.substring(tf.selectionBeginIndex, tf.selectionEndIndex);
				this._table.highLight(selection);
			}
		}

		private function handleTableResize(event:Event):void 
		{
			this._table.bottom = this._table.contentWidth > this._table.width ? 14 : 0;
		}
		
		public function get messages():Vector.<MessageData>
		{
			return this._messages;
		}

		public function get filtered():Vector.<MessageData>
		{
			return this._filtered;
		}
		
	}
}
